using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace Gko.Utils
{
	/// <summary>
	/// 	Represents queue with size limit
	/// </summary>
	/// <typeparam name = "TValue">The type of the value.</typeparam>
	[SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")]
	[SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
#if !SILVERLIGHT
	[Serializable]
#endif
	[ComVisible(false)]
	[DebuggerTypeProxy(typeof(LimitedSizeQueueDebugView<>))]
	[DebuggerDisplay("Count = {Count}")]
	public sealed class LimitedSizeQueue<TValue> : IEnumerable<TValue>, ICollection
	{
		private readonly Queue<TValue> queue;
		private int maxCount;

		/// <summary>
		/// 	Initializes a new instance of the <see cref = "T:Gko.Utils.LimitedSizeQueue`1" /> class.
		/// </summary>
		/// <param name = "maxCount">The maximal items count limit.</param>
		public LimitedSizeQueue(int maxCount)
			: this(maxCount * 2, maxCount)
		{
		}

		/// <summary>
		/// 	Initializes a new instance of the <see cref = "T:Gko.Utils.LimitedSizeQueue`1" /> class.
		/// </summary>
		/// <param name = "collection">The initial collection of items.</param>
		/// <param name = "maxCount">The maximal items count limit.</param>
		/// <exception cref = "ArgumentNullException">Argument is null.</exception>
		public LimitedSizeQueue(IEnumerable<TValue> collection, int maxCount)
		{
			if (collection.IsNull()) {
				throw new ArgumentNullException("collection");
			}
			this.maxCount = maxCount;
			this.queue = new Queue<TValue>(collection);
			this.CheckCount();
		}

		/// <summary>
		/// 	Initializes a new instance of the <see cref = "T:Gko.Utils.LimitedSizeQueue`1" /> class.
		/// </summary>
		/// <param name = "capacity">The capacity.</param>
		/// <param name = "maxCount">The maximal items count limit.</param>
		public LimitedSizeQueue(int capacity, int maxCount)
		{
			this.maxCount = maxCount;
			this.queue = new Queue<TValue>(capacity);
		}

		/// <summary>
		/// 	Gets or sets the maximal items count.
		/// </summary>
		/// <value>The maximal items count.</value>
		public int MaxCount
		{
			get
			{
				return this.maxCount;
			}
			set
			{
				this.maxCount = value;
				this.CheckCount();
			}
		}

		#region ICollection Members

		/// <summary>
		/// 	Copies the elements of the <see cref = "T:System.Collections.ICollection" /> to an <see cref = "T:System.Array" />, starting at a particular <see cref = "T:System.Array" /> index.
		/// </summary>
		/// <param name = "array">The one-dimensional <see cref = "T:System.Array" /> that is the destination of the elements copied from <see cref = "T:System.Collections.ICollection" />. The <see cref = "T:System.Array" /> must have zero-based indexing.</param>
		/// <param name = "index">The zero-based index in <paramref name = "array" /> at which copying begins.</param>
		/// <exception cref = "T:System.ArgumentNullException">
		/// 	<paramref name = "array" /> is null. </exception>
		/// <exception cref = "T:System.ArgumentOutOfRangeException">
		/// 	<paramref name = "index" /> is less than zero. </exception>
		/// <exception cref = "T:System.ArgumentException">
		/// 	<paramref name = "array" /> is multidimensional.-or- <paramref name = "index" /> is equal to or greater than the length of <paramref name = "array" />.-or- The number of elements in the source <see cref = "T:System.Collections.ICollection" /> is greater than the available space from <paramref name = "index" /> to the end of the destination <paramref name = "array" />. </exception>
		/// <exception cref = "T:System.ArgumentException">The type of the source <see cref = "T:System.Collections.ICollection" /> cannot be cast automatically to the type of the destination <paramref name = "array" />. </exception>
		void ICollection.CopyTo(Array array, int index)
		{
			(this.queue as ICollection).CopyTo(array, index);
		}

		/// <summary>
		/// 	Gets the number of elements contained in the <see cref = "T:System.Collections.ICollection" />.
		/// </summary>
		/// <value></value>
		/// <returns>The number of elements contained in the <see cref = "T:System.Collections.ICollection" />.</returns>
		public int Count
		{
			get
			{
				return this.queue.Count;
			}
		}

		/// <summary>
		/// 	Gets a value indicating whether access to the <see cref = "T:System.Collections.ICollection" /> is synchronized (thread safe).
		/// </summary>
		/// <value></value>
		/// <returns>true if access to the <see cref = "T:System.Collections.ICollection" /> is synchronized (thread safe); otherwise, false.</returns>
		bool ICollection.IsSynchronized
		{
			get
			{
				return (this.queue as ICollection).IsSynchronized;
			}
		}

		/// <summary>
		/// 	Gets an object that can be used to synchronize access to the <see cref = "T:System.Collections.ICollection" />.
		/// </summary>
		/// <value></value>
		/// <returns>An object that can be used to synchronize access to the <see cref = "T:System.Collections.ICollection" />.</returns>
		public object SyncRoot
		{
			get
			{
				return (this.queue as ICollection).SyncRoot;
			}
		}

		#endregion

		#region IEnumerable<TValue> Members

		/// <summary>
		/// 	Returns an enumerator that iterates through a collection.
		/// </summary>
		/// <returns>
		/// 	An <see cref = "T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
		/// </returns>
		IEnumerator IEnumerable.GetEnumerator()
		{
			return this.GetEnumerator();
		}

		/// <summary>
		/// 	Returns an enumerator that iterates through the collection.
		/// </summary>
		/// <returns>
		/// 	A <see cref = "T:System.Collections.Generic.IEnumerator`1" /> that can be used to iterate through the collection.
		/// </returns>
		public IEnumerator<TValue> GetEnumerator()
		{
			return this.queue.GetEnumerator();
		}

		#endregion

		private void CheckCount()
		{
			while (this.queue.Count > this.maxCount) {
				var overload = this.queue.Count - this.maxCount;
				var e = new CountExceedEventArgs<TValue>(this, overload);
				if (!this.CountExceed.Raise(this, e)) {
					this.queue.Dequeue(overload);
				}
			}
		}

		/// <summary>
		/// 	Occurs when count of items exceeds limit.
		/// </summary>
		public event EventHandler<CountExceedEventArgs<TValue>> CountExceed;

		/// <summary>
		/// 	Removes all objects from the <see cref = "T:Gko.Utils.LimitedSizeQueue`1" />.
		/// </summary>
		/// <filterpriority>1</filterpriority>
		public void Clear()
		{
			this.queue.Clear();
		}

		/// <summary>
		/// 	Determines whether an element is in the <see cref = "T:Gko.Utils.LimitedSizeQueue`1" />.
		/// </summary>
		/// <returns>true if item is found in the <see cref = "T:Gko.Utils.LimitedSizeQueue`1" />; otherwise, false.</returns>
		/// <param name = "item">The object to locate in the <see cref = "T:Gko.Utils.LimitedSizeQueue`1" />. The value can be null for reference types.</param>
		public bool Contains(TValue item)
		{
			return this.queue.Contains(item);
		}

		/// <summary>
		/// 	Copies the <see cref = "T:Gko.Utils.LimitedSizeQueue`1" /> elements to an existing one-dimensional <see cref = "T:System.Array"></see>, starting at the specified array index.
		/// </summary>
		/// <param name = "array">The one-dimensional <see cref = "T:System.Array"></see> that is the destination of the elements copied from <see cref = "T:Gko.Utils.LimitedSizeQueue`1" />. The <see cref = "T:System.Array"></see> must have zero-based indexing.</param>
		/// <param name = "arrayIndex">The zero-based index in array at which copying begins.</param>
		/// <exception cref = "T:System.ArgumentOutOfRangeException">index is less than zero.</exception>
		/// <exception cref = "T:System.ArgumentNullException">array is null.</exception>
		/// <exception cref = "T:System.ArgumentException">index is equal to or greater than the length of array.-or-The number of elements in the source <see cref = "T:Gko.Utils.LimitedSizeQueue`1" /> is greater than the available space from index to the end of the destination array.</exception>
		public void CopyTo(TValue[] array, int arrayIndex)
		{
			this.queue.CopyTo(array, arrayIndex);
		}

		/// <summary>
		/// 	Removes and returns the object at the beginning of the <see cref = "T:Gko.Utils.LimitedSizeQueue`1" />.
		/// </summary>
		/// <returns>The object that is removed from the beginning of the <see cref = "T:Gko.Utils.LimitedSizeQueue`1" />.</returns>
		/// <exception cref = "T:System.InvalidOperationException">The <see cref = "T:Gko.Utils.LimitedSizeQueue`1" /> is empty.</exception>
		public TValue Dequeue()
		{
			return this.queue.Dequeue();
		}

		/// <summary>
		/// 	Removes and returns the object at the beginning of the <see cref = "T:Gko.Utils.LimitedSizeQueue`1" />.
		/// </summary>
		/// <param name = "count">The count of items to dequeue.</param>
		/// <returns>The objects that are removed from the beginning of the <see cref = "T:Gko.Utils.LimitedSizeQueue`1" />.</returns>
		public IEnumerable<TValue> Dequeue(int count)
		{
			return this.queue.Dequeue(count);
		}

		/// <summary>
		/// 	Adds an object to the end of the <see cref = "T:Gko.Utils.LimitedSizeQueue`1" />.
		/// </summary>
		/// <param name = "item">The object to add to the <see cref = "T:Gko.Utils.LimitedSizeQueue`1" />. The value can be null for reference types.</param>
		public void Enqueue(TValue item)
		{
			this.queue.Enqueue(item);
			this.CheckCount();
		}

		/// <summary>
		/// 	Adds an object to the end of the <see cref = "T:Gko.Utils.LimitedSizeQueue`1" />.
		/// </summary>
		/// <param name = "items">
		/// 	The objects to add to the <see cref = "T:Gko.Utils.LimitedSizeQueue`1" />.
		/// </param>
		public void Enqueue(IEnumerable<TValue> items)
		{
			this.queue.Enqueue(items);
			this.CheckCount();
		}

		/// <summary>
		/// 	Returns the object at the beginning of the <see cref = "T:Gko.Utils.LimitedSizeQueue`1" /> without removing it.
		/// </summary>
		/// <returns>The object at the beginning of the <see cref = "T:Gko.Utils.LimitedSizeQueue`1" />.</returns>
		/// <exception cref = "T:System.InvalidOperationException">The <see cref = "T:Gko.Utils.LimitedSizeQueue`1" /> is empty.</exception>
		public TValue Peek()
		{
			return this.queue.Peek();
		}

		/// <summary>
		/// 	Returns the object at the beginning of the <see cref = "T:Gko.Utils.LimitedSizeQueue`1" /> without removing it.
		/// </summary>
		/// <param name = "count">The count of items to peek.</param>
		/// <returns>Values from queue</returns>
		public IEnumerable<TValue> Peek(int count)
		{
			return this.queue.Peek(count);
		}

		/// <summary>
		/// 	Copies the <see cref = "T:Gko.Utils.LimitedSizeQueue`1" /> elements to a new array.
		/// </summary>
		/// <returns>A new array containing elements copied from the <see cref = "T:Gko.Utils.LimitedSizeQueue`1" />.</returns>
		public TValue[] ToArray()
		{
			return this.queue.ToArray();
		}

		/// <summary>
		/// 	Sets the capacity to the actual number of elements in the <see cref = "T:Gko.Utils.LimitedSizeQueue`1" />, if that number is less than 90 percent of current capacity.
		/// </summary>
		public void TrimExcess()
		{
			this.queue.TrimExcess();
		}
	}
}
